# == cache ==
# save and restore data into/from cache
# - `key` is a dictionary
# - `data` can be anything (i hope)

from pathplus import mkpath
from gzip import GzipFile
#from bz2 import BZ2File
import pickle,os,md5,time


# == cache ==
# save and restore data into/from cache
# - `key` is a dictionary
# - `data` can be anything (i hope)

def hashable(x):
    """
    Cache.
    Return an hashable object that can be used as key in dictionary cache.
    """
    if type(x) in (str,tuple,frozenset,int,float):
        return x
    if type(x) is list:
        return (list,)+tuple(x)
    if type(x) is set:
        return frozenset(x)
    if type(x) is dict:
        tupleslist = []
        for k,v in x.iteritems():
            tupleslist.append( (k,v) )
        return frozenset(tupleslist)

    raise TypeError,"I don't know this type"

def save(key,data,path='.'):
    """
    Cache.
    It stores some *data*  identified by *key* into a file in *path*.
    """

    def lock():
        TIMEOUT = 300 # 5min
        def readall(fname):
            try:
                f = file(fname)
                data = f.read()
                f.close()
            except:
                data = ''
            return data or '0.0'
        def writeall(fname,data):
            f = file(fname,'w')
            f.write(data)
            f.close()
        while True:
            if not os.path.isfile(path+'.lock') or time.time()-float(readall(path+'.lock'))>TIMEOUT:
                writeall(path+'.lock',str(time.time()))
                return

            time.sleep(1)

    def unlock():
        try:
            os.remove(path+'.lock')
        except OSError:
            pass


    mkpath(os.path.split(path)[0])
    lock()
    try:
        # I can't use cachedcache because data might be obsolete.
        d = pickle.load(GzipFile(path))
    except:
        d = {}

    d[hashable(key)] = data
    pickle.dump(d,GzipFile(path,'w'))
    unlock()

    #memory cache
    if not globals().has_key('cachedcache'):
        #print 'create cache'
        globals()['cachedcache'] = {}
    cache = globals()['cachedcache']
    cache[path] = d
    
def load(key,path='.',fault=None):
    """
    Cache.
    Loads data stored by save.
    fault is the value returned if key is not stored in cache.
    """

    #memory cache
    if not globals().has_key('cachedcache'):
        #print 'create cache'
        globals()['cachedcache'] = {}
    cache = globals()['cachedcache']

    if cache.has_key(path):
        #print 'found',path
        if cache[path].has_key(hashable(key)):
            return cache[path][hashable(key)]
        else:
            return fault

    try:
        d = pickle.load(GzipFile(path))
    except:
        return fault

    if d.has_key(hashable(key)):
        data = d[hashable(key)]
    else:
        return fault

    #save in memory cache
    cache[path] = d

    return data

def erase_cachedcache():
    '''
    useful to reload cache from disc
    * doesn't work *
    '''
    globals()['cachedcache'] = {}

def read_c2(path):
    '''return all cache dictionary'''
    return pickle.load(GzipFile(path))

if __name__ == "__main__":
    #test
    hash(hashable(1))
    hash(hashable(3.452))
    hash(hashable(range(5)))
    hash(hashable(tuple(range(5))))
    hash(hashable({1:2,'2':'tre',4.0:''}))


